Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | export const dynamic = "force-dynamic"; /** * Admin Ticket Assignment API * POST /api/admin/support/tickets/[id]/assign - Assign ticket to agent */ import { NextRequest, NextResponse } from 'next/server'; import { requireAdminRole, handleAuthError } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { AssignTicketSchema } from '@/lib/validation/support-schemas'; import { recordAssignment } from '@/lib/support/ticket-utils'; import { notifyAgentAssigned } from '@/lib/support/notification-utils'; import { TicketStatus, SupportTicketWithRelations } from '@/types/support'; import { handleError } from '@/lib/error-handler'; import { logger } from '@/lib/logging'; interface RouteParams { params: Promise<{ id: string }>; } export async function POST(request: NextRequest, { params }: RouteParams) { try { const session = await requireAdminRole(); const { id } = await params; const adminId = Number(session.user.id); // Get current ticket const ticket = await prisma.supportTicket.findUnique({ where: { id }, select: { id: true, ticketNumber: true, assignedToId: true, status: true}}); if (!ticket) { return NextResponse.json( { success: false, error: 'Ticket not found' }, { status: 404 } ); } // Validate input const body = await request.json(); const validationResult = AssignTicketSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { success: false, error: 'Validation failed', details: validationResult.error.flatten().fieldErrors}, { status: 400 } ); } const { assignedToId } = validationResult.data; // Verify the agent exists and is an admin if (assignedToId) { const agent = await prisma.user.findUnique({ where: { id: assignedToId }, select: { id: true, role: true }}); if (!agent || agent.role !== 'ADMIN') { return NextResponse.json( { success: false, error: 'Invalid agent' }, { status: 400 } ); } } // Build update data const updateData: Record<string, unknown> = { assignedToId}; // If assigning and ticket is OPEN, move to IN_PROGRESS if (assignedToId && ticket.status === TicketStatus.OPEN) { updateData.status = TicketStatus.IN_PROGRESS; } // Set first response time if this is the first assignment if (assignedToId && !ticket.assignedToId) { updateData.firstResponseAt = new Date(); } // Update the ticket const updatedTicket = await prisma.supportTicket.update({ where: { id }, data: updateData, include: { user: { select: { id: true, name: true, email: true }}, assignedTo: { select: { id: true, name: true, email: true }}}}); // Record in history await recordAssignment(id, ticket.assignedToId, assignedToId, adminId); // Notify new agent if (assignedToId) { await notifyAgentAssigned( updatedTicket as unknown as SupportTicketWithRelations, assignedToId ); // Update agent profile stats await prisma.supportAgentProfile.upsert({ where: { userId: assignedToId }, update: { totalTickets: { increment: 1 }}, create: { userId: assignedToId, totalTickets: 1}}); } logger.info('Ticket assigned', { category: 'ADMIN_SUPPORT', ticketId: id, ticketNumber: ticket.ticketNumber, oldAssignee: ticket.assignedToId, newAssignee: assignedToId, adminId}); return NextResponse.json({ success: true, data: updatedTicket, message: assignedToId ? `Ticket assigned to ${updatedTicket.assignedTo?.name || 'agent'}` : 'Ticket unassigned'}); } catch (error) { const authResponse = handleAuthError(error as Error); if (authResponse) return authResponse; logger.error('Error assigning ticket', error instanceof Error ? error : new Error(String(error)), { category: 'ADMIN_SUPPORT' }); return handleError(error); } } |